package com.liaoyifan.core.util;

import com.liaoyifan.core.valid.Assert;
import com.liaoyifan.core.valid.Lang;
import jakarta.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.*;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

@SuppressWarnings("unused")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Log4j2
public class Download {

    // Redisson键前缀
    private static final String DOWNLOAD_RATE_LIMIT_PREFIX = "core_download:rate_limit:";
    // 默认缓冲区大小
    private static final int BUFFER_SIZE = 8 * 1024; // 8KB
    // 默认限速
    private static final long LIMIT_KB = 2048;
    // HTTP日期格式（RFC 1123）
    private static final List<DateTimeFormatter> HTTP_DATE_FORMATTERS =
            Arrays.asList(
                    DateTimeFormatter.RFC_1123_DATE_TIME, // 对应"EEE, dd MMM yyyy HH:mm:ss zzz"
                    DateTimeFormatter.ofPattern("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
                    DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy", Locale.US));

    /**
     * 使用StreamingResponseBody进行下载（支持断点续传和ETag验证）
     *
     * @param request 当前请求
     * @param file 文件
     * @param clientKey 客户标识, 用于限流
     * @return ResponseEntity
     */
    public static ResponseEntity<StreamingResponseBody> start(
            HttpServletRequest request, File file, String clientKey) throws IOException {
        return start(request, file, clientKey, LIMIT_KB);
    }

    /**
     * 使用StreamingResponseBody进行下载（支持断点续传和ETag验证）
     *
     * @param request 当前请求
     * @param file 文件
     * @param clientKey 客户标识, 用于限流
     * @param rateLimit 每个客户的总速率限制（KB/s）
     * @return ResponseEntity
     */
    public static ResponseEntity<StreamingResponseBody> start(
            HttpServletRequest request, File file, String clientKey, long rateLimit)
            throws IOException {
        // 从当前请求中获取Range和If-Range头
        String rangeHeader = request.getHeader(HttpHeaders.RANGE);
        String ifRangeHeader = request.getHeader(HttpHeaders.IF_RANGE);
        Assert.isTrue(rateLimit > 0, "limitKb must be greater than 0");
        Assert.notBlank(clientKey, "client cannot be blank");
        if (!file.exists() || !file.isFile()) {
            throw new FileNotFoundException(file.getAbsolutePath());
        }
        try {
            long fileLength = file.length();
            long startPos = 0;
            long endPos = fileLength - 1;
            boolean isPartial = false;
            // 生成ETag（基于文件大小和最后修改时间, 确保文件未修改时ETag不变）
            String etag = generateETag(file);
            // 获取文件最后修改时间（毫秒）
            long lastModified = file.lastModified();
            // 处理If-Range：验证资源是否变化, 变化则忽略Range返回完整文件
            if (Lang.isNotBlank(ifRangeHeader)) {
                if (!validateIfRange(ifRangeHeader, etag, lastModified)) {
                    log.info(
                            "File is Modified, Client If-Range: {}, Server ETag: {}",
                            ifRangeHeader,
                            etag);
                    rangeHeader = null; // 忽略Range, 返回完整文件
                }
            }
            // 处理断点续传（仅当Range有效且If-Range验证通过时）
            if (Lang.isNotBlank(rangeHeader) && rangeHeader.startsWith("bytes=")) {
                isPartial = true;
                String range = rangeHeader.substring("bytes=".length()).trim();
                String[] ranges = range.split("-");
                try {
                    startPos = Long.parseLong(ranges[0]);
                    // 检查起始位置是否有效
                    if (startPos < 0 || startPos >= fileLength) {
                        log.warn("Invalid startPos: {}, file length: {}", startPos, fileLength);
                        return ResponseEntity.status(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
                                .build();
                    }
                    if (ranges.length > 1 && StringUtils.hasText(ranges[1])) {
                        endPos = Long.parseLong(ranges[1]);
                        // 检查结束位置是否有效
                        if (endPos >= fileLength || endPos < startPos) {
                            log.warn("Invalid endPos: {}, file length: {}", endPos, fileLength);
                            return ResponseEntity.status(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
                                    .build();
                        }
                    }
                } catch (NumberFormatException e) {
                    log.error("Parse range header error: {}", rangeHeader, e);
                    return ResponseEntity.badRequest().build();
                }
            }

            long contentLength = endPos - startPos + 1;
            // 设置响应头（包含ETag和Last-Modified）
            HttpHeaders headers =
                    createHeaders(
                            file,
                            fileLength,
                            startPos,
                            endPos,
                            isPartial,
                            contentLength,
                            etag,
                            lastModified);
            final long pos = startPos;
            StreamingResponseBody body =
                    out -> {
                        try (RandomAccessFile raf = new RandomAccessFile(file, "r");
                                OutputStream outputStream =
                                        new ThrottledOutputStream(out, clientKey, rateLimit)) {

                            raf.seek(pos); // 定位到起始位置
                            byte[] buffer = new byte[BUFFER_SIZE];
                            long remaining = contentLength;

                            while (remaining > 0) {
                                int read =
                                        raf.read(
                                                buffer,
                                                0,
                                                (int) Math.min(buffer.length, remaining));
                                if (read == -1) break;

                                outputStream.write(buffer, 0, read);
                                remaining -= read;

                                // 每写入1MB数据就刷新一次, 防止缓冲区溢出
                                if (remaining % (1024 * 1024) == 0) {
                                    outputStream.flush();
                                }
                            }
                            outputStream.flush();
                            log.info(
                                    "File download success: {}, client: {}, bytes: {}",
                                    file.getName(),
                                    clientKey,
                                    contentLength - remaining);
                        } catch (Exception e) {
                            log.error("File download error: {}", file.getAbsolutePath(), e);
                            // 不需要重新抛出异常, Spring会处理
                        }
                    };

            return ResponseEntity.status(isPartial ? HttpStatus.PARTIAL_CONTENT : HttpStatus.OK)
                    .headers(headers)
                    .body(body);
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    /** 生成ETag（基于文件大小和最后修改时间, 强验证） 格式："文件大小-最后修改时间（毫秒）"（双引号包裹, 符合HTTP规范） */
    private static String generateETag(File file) {
        long length = file.length();
        long lastModified = file.lastModified();
        // 强ETag：字节级精确匹配（文件内容变化则ETag必变）
        return String.format("\"%d-%d\"", length, lastModified);
    }

    /** 验证If-Range头是否有效 If-Range可以是ETag或Last-Modified时间, 需与当前资源匹配 */
    private static boolean validateIfRange(String ifRangeHeader, String etag, long lastModified) {
        // 1. 先尝试匹配ETag（If-Range可能是ETag, 带双引号）
        if (ifRangeHeader.equals(etag)) {
            return true;
        }
        // 2. 尝试解析为Last-Modified时间（HTTP日期格式）
        try {
            long ifRangeTime = parseHttpDate(ifRangeHeader);
            // 允许1秒误差（文件修改时间精度可能到秒）
            return Math.abs(ifRangeTime - lastModified) <= 1000;
        } catch (DateTimeParseException e) {
            // 无法解析为时间, 视为无效
            return false;
        }
    }

    /** 解析HTTP日期格式（支持多种常见格式） */
    private static long parseHttpDate(String dateString) throws DateTimeParseException {
        for (DateTimeFormatter formatter : HTTP_DATE_FORMATTERS) {
            try {
                return ZonedDateTime.parse(dateString, formatter).toInstant().toEpochMilli();
            } catch (DateTimeParseException e) {
                // 尝试下一种格式
            }
        }
        throw new DateTimeParseException("Cannot parse date: " + dateString, dateString, 0);
    }

    /** 创建响应头（包含ETag和Last-Modified） */
    private static HttpHeaders createHeaders(
            File file,
            long fileLength,
            long startPos,
            long endPos,
            boolean isPartial,
            long contentLength,
            String etag,
            long lastModified) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDisposition(
                ContentDisposition.attachment().filename(encodeFilename(file.getName())).build());
        headers.set(HttpHeaders.ACCEPT_RANGES, "bytes");
        headers.setContentLength(contentLength);
        // 添加ETag和Last-Modified（核心：支持续传验证）
        headers.setETag(etag);
        headers.setLastModified(lastModified);
        if (isPartial) {
            headers.set(
                    HttpHeaders.CONTENT_RANGE,
                    "bytes " + startPos + "-" + endPos + "/" + fileLength);
        }
        // 添加缓存控制(允许浏览器缓存ETag和Last-Modified)
        headers.setCacheControl(CacheControl.maxAge(Duration.ofDays(7)).cachePublic()); // 缓存2天
        headers.setPragma("public");
        return headers;
    }

    /** 带总速率限制的输出流 */
    private static class ThrottledOutputStream extends FilterOutputStream {

        private final String clientKey;
        private final long rateLimit;

        public ThrottledOutputStream(OutputStream out, String clientKey, long rateLimit) {
            super(out);
            this.clientKey = clientKey;
            this.rateLimit = rateLimit;
        }

        @SuppressWarnings("NullableProblems")
        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            int remaining = len;
            int currentOffset = off;

            while (remaining > 0) {
                // 每次传输的字节数不超过缓冲区大小（避免单次请求过大）
                int chunkSize = Math.min(remaining, BUFFER_SIZE);

                // 获取传输chunkSize字节的许可（会阻塞直到获取到足够许可）
                // 这一步是限速核心：Redisson会自动控制单位时间内的总传输量
                Redis.acquire(
                        DOWNLOAD_RATE_LIMIT_PREFIX + clientKey, rateLimit * 1024, 1, chunkSize);

                // 写入数据块
                out.write(b, currentOffset, chunkSize);

                // 更新剩余字节数和偏移量
                currentOffset += chunkSize;
                remaining -= chunkSize;
            }
        }

        @Override
        public void write(int b) throws IOException {
            byte[] singleByte = new byte[1];
            singleByte[0] = (byte) b;
            write(singleByte, 0, 1);
        }
    }

    /** 编码文件名, 防止中文乱码 */
    private static String encodeFilename(String filename) {
        return URLEncoder.encode(FileName.removeUUID(filename), StandardCharsets.UTF_8)
                .replace("+", "%20");
    }
}
